package com.xxchat.websocket.utils;

import com.alibaba.fastjson.JSON;
import com.xxchat.entity.constants.SysConstants;
import com.xxchat.entity.dto.MessageSendDTO;
import com.xxchat.entity.dto.WsInitData;
import com.xxchat.entity.enums.MessageTypeEnum;
import com.xxchat.entity.enums.UserContactApplyStatusEnum;
import com.xxchat.entity.enums.UserContactTypeEnum;
import com.xxchat.entity.po.ChatMessage;
import com.xxchat.entity.po.ChatSessionUser;
import com.xxchat.entity.po.UserContactApply;
import com.xxchat.entity.po.UserInfo;
import com.xxchat.entity.query.ChatMessageQuery;
import com.xxchat.entity.query.UserContactApplyQuery;
import com.xxchat.entity.query.UserInfoQuery;
import com.xxchat.mappers.ChatMessageMapper;
import com.xxchat.mappers.UserContactApplyMapper;
import com.xxchat.mappers.UserInfoMapper;
import com.xxchat.redis.RedisComponent;
import com.xxchat.service.ChatSessionUserService;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Component
public class ChannelContextUtils {
    private static final Logger log = LoggerFactory.getLogger(ChannelContextUtils.class);

    private static final ConcurrentHashMap<String, Channel> CHANNEL_MAP = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, ChannelGroup> CHANNEL_GROUP = new ConcurrentHashMap<>();

    @Resource
    private RedisComponent redisComponent;

    @Resource
    private UserInfoMapper<UserInfo, UserInfoQuery> userInfoMapper;

    @Resource
    private ChatSessionUserService chatSessionUserService;

    @Resource
    private ChatMessageMapper<ChatMessage, ChatMessageQuery> chatMessageMapper;

    @Resource
    private UserContactApplyMapper<UserContactApply, UserContactApplyQuery> userContactApplyMapper;

    public void addContext(String userId, Channel channel) {
        // 获取Channel的唯一ID(channelId)
        String channelId = channel.id().toString();
        AttributeKey<String> key = null;
        // 检查是否已存在以这个channelId命名的AttributeKey
        if (!AttributeKey.exists(channelId)) {
            // 如果不存在，就创建一个新的AttributeKey
            key = AttributeKey.newInstance(channelId);
        } else {
            // 如果已存在，就获取现有的AttributeKey
            key = AttributeKey.valueOf(channelId);
        }
        channel.attr(key).set(userId);

        List<String> contactIds = redisComponent.getUserContactList(userId);
        if (null != contactIds && !contactIds.isEmpty()) {
            // 群组处理
            for (String groupId : contactIds) {
                if (groupId.startsWith(UserContactTypeEnum.GROUP.getPrefix())) {
                    add2Group(groupId, channel);
                }
            }
        }

        // 联系人处理
        CHANNEL_MAP.put(userId, channel);
        // 立即更新一次心跳
        redisComponent.saveUserHeartBeat(userId);

        // 给用户发送消息
        UserInfo userInfo = this.userInfoMapper.selectByUserId(userId);
        if (null == userInfo) {
            return;
        }
        Date lastOffTime = userInfo.getLastOffTime();
        if (null != lastOffTime) {
            // 计算3天前的毫秒时间点
            long threeDaysAgo = System.currentTimeMillis() - SysConstants.MILLIS_SECONDS_3DAYS_AGO;

            // 比较最后离线时间是否早于3天前
            if (lastOffTime.getTime() < threeDaysAgo) {
                // 如果超过3天没上线，则把最后离线时间设为3天前这个时间点
                lastOffTime = new Date(threeDaysAgo);
            }
        }

        // 查询会话信息，查询用户所有的会话信息，保证换了设备会话同步
        List<ChatSessionUser> chatSessionUserList = chatSessionUserService.findSessionList(userId);
        WsInitData wsInitData = new WsInitData();
        wsInitData.setChatSessionUserList(chatSessionUserList);

        // 查询聊天消息
        List<String> groupIds = new ArrayList<>();
        if (contactIds != null) {
            groupIds = contactIds.stream().filter(item -> item.startsWith(UserContactTypeEnum.GROUP.getPrefix())).collect(Collectors.toList());
        }
        groupIds.add(userId);
        List<ChatMessage> chatMessageList = this.chatMessageMapper.selectMyMessageList(groupIds, lastOffTime);
        wsInitData.setChatMessageList(chatMessageList);

        // 查询好友申请数
        UserContactApplyQuery query = new UserContactApplyQuery();
        query.setReceiveUserId(userId);
        query.setStatus(UserContactApplyStatusEnum.PENDING.getStatus());
        Integer count = this.userContactApplyMapper.selectCount(query);
        wsInitData.setApplyCount(count);

        // 发送消息
        MessageSendDTO<WsInitData> messageSendDTO = new MessageSendDTO<WsInitData>();
        messageSendDTO.setMessageType(MessageTypeEnum.INIT.getType());
        messageSendDTO.setExtendData(wsInitData);
        sendMessage(messageSendDTO, userId);
    }

    // 发消息
    public void sendMessage(MessageSendDTO messageSendDTO, String receiverId) {
        Channel channel = CHANNEL_MAP.get(receiverId);
        if (null == channel) {
            return;
        }

        if (MessageTypeEnum.ADD_FRIEND.getType().equals(messageSendDTO.getMessageType())) {
            // 给自己发
            UserInfo userInfo = (UserInfo) messageSendDTO.getExtendData();
            messageSendDTO.setMessageType(MessageTypeEnum.ADD_FRIEND.getType());
            messageSendDTO.setContactId(userInfo.getUserId());
            messageSendDTO.setContactName(userInfo.getNickName());
            messageSendDTO.setExtendData(null);
        } else {
            // 对于客户端而言，这里的发送者就是联系人
            messageSendDTO.setContactId(messageSendDTO.getSendUserId());
            messageSendDTO.setContactName(messageSendDTO.getSendUserNickname());
        }
        channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(messageSendDTO)));
    }

    // 发消息
    public void sendMessage(MessageSendDTO messageSendDTO) {
        UserContactTypeEnum contactTypeEnum = UserContactTypeEnum.getByPrefix(messageSendDTO.getContactId());
        if (null == contactTypeEnum) {
            return;
        }
        switch (contactTypeEnum) {
            case USER:
                this.send2User(messageSendDTO);
                break;
            case GROUP:
                this.send2Group(messageSendDTO);
                break;
        }
    }

    // 发送用户
    private void send2User(MessageSendDTO messageSendDTO) {
        String contactId = messageSendDTO.getContactId();
        if (StringUtils.isEmpty(contactId)) {
            return;
        }
        this.sendMessage(messageSendDTO, contactId);
        // 强制下线
        if (MessageTypeEnum.FORCE_OFF_LINE.getType().equals(messageSendDTO.getMessageType())) {
            this.closeContext(contactId);
        }
    }

    // 发送群聊
    private void send2Group(MessageSendDTO messageSendDTO) {
        String contactId = messageSendDTO.getContactId();
        if (StringUtils.isEmpty(contactId)) {
            return;
        }
        ChannelGroup channelGroup = CHANNEL_GROUP.get(contactId);
        if (null == channelGroup) {
            return;
        }
        channelGroup.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(messageSendDTO)));

        // 移除群聊
        MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByType(messageSendDTO.getMessageType());
        if (MessageTypeEnum.LEAVE_GROUP.equals(messageTypeEnum) || MessageTypeEnum.REMOVE_GROUP.equals(messageTypeEnum)) {
            String userId = (String) messageSendDTO.getExtendData();
            redisComponent.removeUserContact(userId, contactId);
            Channel channel = CHANNEL_MAP.get(userId);
            if (null != channel) {
                channelGroup.remove(channel);
            }
        }
        // 解散群聊
        if (MessageTypeEnum.DISSOLUTION_GROUP.equals(messageTypeEnum)) {
            CHANNEL_GROUP.remove(contactId);
            channelGroup.close();
        }
    }

    private void add2Group(String groupId, Channel channel) {
        ChannelGroup channelGroup = CHANNEL_GROUP.get(groupId);
        if (null == channelGroup) {
            channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            CHANNEL_GROUP.put(groupId, channelGroup);
        }
        if (null == channel) {
            return;
        }
        channelGroup.add(channel);
    }

    public void addUser2Group(String userId, String groupId) {
        Channel channel = CHANNEL_MAP.get(userId);
        add2Group(groupId, channel);
    }

    // TODO 有问题：一个群组断开一次就更新一次离线时间，如果多个群组断开，只记录最后一次？
    public void removeContext(Channel channel) {
        Attribute<String> attribute = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attribute.get();
        if (StringUtils.isEmpty(userId)) {
            return;
        }
        CHANNEL_MAP.remove(userId);
        redisComponent.cleanUserHeartBeat(userId);
        // 更新用户最后离线时间
        UserInfo userInfo = new UserInfo();
        userInfo.setLastOffTime(new Date());
        this.userInfoMapper.updateByUserId(userInfo, userId);
    }

    public void closeContext(String userId) {
        if (StringUtils.isEmpty(userId)) {
            return;
        }
        redisComponent.cleanTokenByUserId(userId);
        Channel channel = CHANNEL_MAP.get(userId);
        if (null == channel) {
            return;
        }
        channel.close();
    }
}
